Skip to content

Conversation

cactuser-Lu
Copy link

@cactuser-Lu cactuser-Lu commented Sep 30, 2025

🤔 This is a ...

  • New feature

支持键盘 Esc 关闭 \左右切换

Summary by CodeRabbit

  • 新功能
    • Tour 组件支持键盘导航:Esc 关闭引导,左右方向键在步骤间切换。
    • 新增可选属性 keyboard(默认开启)用于启用/禁用键盘操作。

Copy link

vercel bot commented Sep 30, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
tour Ready Ready Preview Comment Sep 30, 2025 6:38am

Copy link

coderabbitai bot commented Sep 30, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

为 Tour 组件新增键盘导航能力:新增可选属性 keyboard(默认 true),在打开时注册键盘事件处理,支持 Esc 关闭与左右方向键切换步骤;关闭时注销事件。同步接入现有 onChange/onInternalChange/onClose 流程。对外接口在 TourProps 中新增 keyboard 字段。

Changes

Cohort / File(s) Summary
组件键盘交互实现
src/Tour.tsx
新增 keyboard 属性与键盘事件处理逻辑;在 open === true 时注册、在关闭时注销键盘处理;忽略输入类元素的按键;支持 Esc 关闭与左右方向键步骤切换;通过 handleClose / onInternalChange / onChange 统一流程触发关闭与切换。
公开接口扩展
src/interface.ts
TourProps 中新增可选字段 keyboard?: boolean,默认行为保持兼容(默认启用)。

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User as 用户
  participant KB as 键盘事件
  participant Tour as Tour 组件
  participant Host as 调用方(回调)

  rect rgba(227,242,253,0.5)
    Note over Tour: 当 Tour.open === true 且 keyboard 为 true 时注册键盘处理器
  end

  User->>KB: 按键 (Esc / ArrowLeft / ArrowRight)
  KB->>Tour: keyboardHandler(event)
  alt Esc
    Tour->>Tour: 调用 handleClose()
    Tour->>Host: onClose()
  else ArrowLeft/ArrowRight
    Tour->>Tour: 计算目标步骤索引
    Tour->>Host: onInternalChange(nextIndex)
    Tour->>Host: onChange(nextIndex)  (如有外部绑定)
  end

  rect rgba(232,245,233,0.5)
    Note over Tour: 当 open 为 false 或卸载时注销键盘处理器
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • zombieJ

Poem

小兔跃键映光斑,
左右轻敲步步翻。
Esc一按帷幕阖,
指尖微风导游船。
新键入场不惊扰,
旧流相接更周全。 🐇⌨️

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed 该标题简洁明了地传达了本次 PR 的核心功能——为 Tour 组件的步骤增加键盘事件支持,与变更集内容高度一致且易于被团队成员快速理解。
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🧪 Early access (Sonnet 4.5): enabled

We are currently testing the Sonnet 4.5 model, which is expected to improve code review quality. However, this model may lead to increased noise levels in the review comments. Please disable the early access features if the noise level causes any inconvenience.

Note:

  • Public repositories are always opted into early access features.
  • You can enable or disable early access features from the CodeRabbit UI or by updating the CodeRabbit configuration file.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

Summary of Changes

Hello @cactuser-Lu, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求通过引入键盘事件支持,显著提升了 Tour 组件的用户体验和可访问性。现在用户可以通过键盘快捷键关闭 Tour 或在不同步骤之间切换,这使得交互更加流畅和便捷。

Highlights

  • 键盘事件支持: 为 Tour 组件的步骤添加了键盘事件支持,允许用户通过键盘进行交互。
  • Esc 键关闭: 用户现在可以使用 Esc 键关闭 Tour。
  • 左右箭头导航: 用户可以使用 左箭头右箭头 键在 Tour 步骤之间进行导航。
  • keyboard 属性: 引入了一个新的 keyboard 属性,默认为 true,允许开发者控制是否启用键盘交互功能。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces keyboard navigation to the Tour component, which is a great enhancement for accessibility. The implementation is well done. I have one suggestion to refactor the keyboard event handler to use modern browser APIs and improve its structure, which should also fix a minor bug. Additionally, there's a small typo in the pull request title ('keyborad' should be 'keyboard').

Comment on lines 168 to 193
const keyboardHandler = useEvent((e: KeyboardEvent) => {
if (keyboard && e.keyCode === KeyCode.ESC) {
if (mergedClosable !== null) {
e.stopPropagation();
e.preventDefault();
handleClose();
}
return;
}

if (keyboard && e.keyCode === KeyCode.LEFT) {
if (mergedCurrent > 0) {
e.preventDefault();
onInternalChange(mergedCurrent - 1);
}
return;
}

if (keyboard && e.keyCode === KeyCode.RIGHT) {
e.preventDefault();
if (mergedCurrent < steps.length - 1) {
onInternalChange(mergedCurrent + 1);
}
return;
}
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The keyboard handler implementation can be improved for better readability, correctness, and to adhere to modern web standards.

  • Use e.key instead of e.keyCode: The keyCode property is deprecated. It's better to use e.key for future-proofing and consistency with modern web APIs. This change would also allow for the removal of the KeyCode import.
  • Conditional preventDefault: The e.preventDefault() for the right arrow key is called unconditionally. It should only be called when the step actually changes (i.e., inside the if block), similar to the logic for the left arrow key. This prevents blocking the default browser behavior when at the last step.
  • Improved Structure: A switch statement on e.key would be more readable and maintainable than a series of if statements. The if (keyboard) check can also be done once at the beginning of the function.

Here is a suggested refactoring that applies these points:

  const keyboardHandler = useEvent((e: KeyboardEvent) => {
    if (!keyboard) {
      return;
    }

    switch (e.key) {
      case 'Escape':
        if (mergedClosable !== null) {
          e.stopPropagation();
          e.preventDefault();
          handleClose();
        }
        break;
      case 'ArrowLeft':
        if (mergedCurrent > 0) {
          e.preventDefault();
          onInternalChange(mergedCurrent - 1);
        }
        break;
      case 'ArrowRight':
        if (mergedCurrent < steps.length - 1) {
          e.preventDefault();
          onInternalChange(mergedCurrent + 1);
        }
        break;
      default:
        // Do nothing
        break;
    }
  });

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/interface.ts (1)

61-61: 建议添加 JSDoc 注释说明 keyboard 属性的用途。

新增的 keyboard 属性缺少文档注释,建议添加说明以提高 API 的可读性和可维护性。例如:

   open?: boolean;
+  /** Whether to enable keyboard interaction (Esc to close, Left/Right to navigate). Default: true */
   keyboard?: boolean;
   defaultOpen?: boolean;
src/Tour.tsx (1)

195-201: 考虑使用 useEffect 代替 useLayoutEffect

事件监听器的注册和清理逻辑正确,依赖项数组也准确。但是,对于键盘事件监听器的注册,使用 useEffect 而非 useLayoutEffect 更合适,因为这个操作不需要在浏览器绘制之前同步执行,使用 useEffect 可以避免阻塞视觉更新。

建议修改:

- useLayoutEffect(() => {
+ useEffect(() => {
    if (!mergedOpen) return;
    window.addEventListener('keydown', keyboardHandler);
    return () => {
      window.removeEventListener('keydown', keyboardHandler);
    };
  }, [mergedOpen, keyboardHandler]);

注意:需要在文件顶部添加 useEffect 的导入:

- import { useMemo } from 'react';
+ import { useMemo, useEffect } from 'react';
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c25065a and a343a90.

📒 Files selected for processing (2)
  • src/Tour.tsx (4 hunks)
  • src/interface.ts (1 hunks)
🔇 Additional comments (3)
src/Tour.tsx (3)

7-8: 代码更改看起来不错!

正确引入了 useEventKeyCode 工具,用于实现键盘事件处理。


39-39: 代码更改看起来不错!

keyboard 属性默认值为 true 是合理的设计,既启用了新功能,又保持了向后兼容性。


161-164: 代码更改看起来不错!

将关闭逻辑提取为 handleClose 函数是良好的代码组织实践,便于在多处复用(键盘处理、按钮点击、完成回调)。

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/Tour.tsx (1)

168-204: 建议采用现代 API 并改进代码结构。

当前实现已经解决了之前评审中关于 preventDefault 调用时机和输入元素过滤的问题,这很好!但仍有改进空间:

  1. 使用 e.key 替代 e.keyCodekeyCode 已被弃用,应使用 e.key 以符合现代 Web 标准。
  2. 使用 switch 语句:相比多个 if 语句,switch 语句可读性和可维护性更好。

应用以下差异来改进实现:

  const keyboardHandler = useEvent((e: KeyboardEvent) => {
    // Ignore keyboard events from input-like elements to avoid interfering when typing
    const el = e.target as HTMLElement | null;
    if (
        el?.tagName === 'INPUT' ||
        el?.tagName === 'TEXTAREA' ||
        el?.tagName === 'SELECT' ||
        el?.isContentEditable
      ) {
        return;
      }

-   if (keyboard && e.keyCode === KeyCode.ESC) {
-     if (mergedClosable !== null) {
-       e.stopPropagation();
-       e.preventDefault();
-       handleClose();
-     }
-     return;
-   }
-
-   if (keyboard && e.keyCode === KeyCode.LEFT) {
-     if (mergedCurrent > 0) {
-       e.preventDefault();
-       onInternalChange(mergedCurrent - 1);
-     }
-     return;
-   }
-
-   if (keyboard && e.keyCode === KeyCode.RIGHT) {
-     if (mergedCurrent < steps.length - 1) {
-       e.preventDefault();
-       onInternalChange(mergedCurrent + 1);
-     }
-     return;
-   }
+   if (!keyboard) {
+     return;
+   }
+
+   switch (e.key) {
+     case 'Escape':
+       if (mergedClosable !== null) {
+         e.stopPropagation();
+         e.preventDefault();
+         handleClose();
+       }
+       break;
+     case 'ArrowLeft':
+       if (mergedCurrent > 0) {
+         e.preventDefault();
+         onInternalChange(mergedCurrent - 1);
+       }
+       break;
+     case 'ArrowRight':
+       if (mergedCurrent < steps.length - 1) {
+         e.preventDefault();
+         onInternalChange(mergedCurrent + 1);
+       }
+       break;
+     default:
+       break;
+   }
  });
🧹 Nitpick comments (1)
src/Tour.tsx (1)

7-8: 建议使用现代的 e.key API 替代已弃用的 KeyCode

KeyCode 已被弃用,建议使用 e.key 属性来检测按键。这样可以移除对 KeyCode 的导入依赖,并使代码更符合现代 Web 标准。

应用以下差异以移除 KeyCode 导入:

 import useLayoutEffect from '@rc-component/util/lib/hooks/useLayoutEffect';
 import useEvent from '@rc-component/util/lib/hooks/useEvent';
-import KeyCode from '@rc-component/util/lib/KeyCode';
 import useControlledState from '@rc-component/util/lib/hooks/useControlledState';
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a343a90 and 551f26c.

📒 Files selected for processing (1)
  • src/Tour.tsx (4 hunks)
🔇 Additional comments (4)
src/Tour.tsx (4)

39-39: 默认启用键盘支持是合理的设计!

keyboard 默认设置为 true 符合渐进增强原则,为大多数用户提供更好的可访问性体验。


93-93: 依赖项更新正确!

setMergedCurrent 添加到依赖数组中符合 React Hooks 的最佳实践,即使该函数引用通常是稳定的。


161-164: 提取 handleClose 函数提高了代码复用性!

将关闭逻辑提取为独立函数是良好的重构实践,使得键盘处理和按钮点击可以共享相同的关闭逻辑。


206-212: 事件监听器的注册和清理实现正确!

使用 useLayoutEffect 在 tour 打开时注册全局键盘事件监听器,并在关闭或组件卸载时正确清理,避免了内存泄漏。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant